package org.monkey.spring;


import lombok.extern.slf4j.Slf4j;
import org.monkey.spring.factory.ObjectFactory;

import java.beans.Introspector;
import java.io.File;
import java.lang.reflect.*;
import java.net.URL;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

/**
 * obj类---> 推断构造方法---> 普通对象---> 依赖注入 ---> 初始化前(@PostConstruct)---> 初始化(InitializingBean) ---> 初始化后（AOP）
 * ---> 代理对象---> 放入单例池Map---> Bean
 */
@Slf4j
public class MonkeyApplicationContext {

    private static final String SINGLETON = "singleton";

    private Class configClass;

    //bean单例池 存放经过完整生命周期后的bean对象   // 一级缓存
    private ConcurrentMap<String, Object> singletonObjects = new ConcurrentHashMap<>();
    // 二级缓存，存放属性为设置的不完整的bean,降低锁的粒度，提高性能
    private Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>();

    // 三级缓存  只能创建一次代理对象， 存储 循环依赖对象创建动态代理对象 提升了扩展性，保证了bean的规范
    private Map<String, ObjectFactory> singletonFactories = new ConcurrentHashMap<>();

    // 循环依赖标识
    public static Set<String> singletonsCurrennlyInCreation = new HashSet<>();


    // bean定义池
    private ConcurrentMap<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();

    private Map<Class<?>, ArrayList<BeanDefinition>> beanDefinitionByClass = new ConcurrentHashMap<>();

    private List<String> beanDefinitionNames = new ArrayList<>();

    private List<BeanPostProcessor> beanPostProcessorList = new ArrayList<>();

    private List<String> beanNames = new ArrayList<>();

    public MonkeyApplicationContext(Class configClass) {
        log.info("UU ApplicationContext start...");
        this.configClass = configClass;

        // ComponentScan注解 --> 扫描路径 --> 扫描 --> BeanDefinition --> BeanDefinitionMap
        scan(configClass);
        // 实例化单例 存入单例池
        preInstantiateSingletons();
    }

    /**
     * 根据参数类进行扫描
     *
     * @param configClass configClass
     */
    private void scan(Class configClass) {
        //处理ComponentScan注解
        List<Class<?>> classList = handleComponentsScanAnnotation(configClass);
        log.info("scan class count {}", classList.size());
        // 处理 Component注解  todo 有点乱跳
        handleComponentAnnotation(classList);
        //处理Imported注解
        this.handleImportedAnnotation(configClass);
        // 处理Bean 注解
        this.handleBeanAnnotation(configClass);

    }

    /**
     * 扫描配置路径下的class
     *
     * @param configClass configClass
     * @return
     */
    private List<Class<?>> handleComponentsScanAnnotation(Class configClass) {
        // 存放扫描到的bean
        List<Class<?>> classList = new ArrayList<>();
        // 解析配置类
        // ComponentScan注解 --> 扫描路径 --> 扫描
        if (configClass.isAnnotationPresent(ComponentScan.class)) {
            ComponentScan componentScanAnno = (ComponentScan) configClass.getDeclaredAnnotation(ComponentScan.class);
            String[] arrayPath = componentScanAnno.value();
            String[] basePackages = componentScanAnno.basePackages();

            List<String> pathList = new ArrayList<>();
            pathList.addAll(Arrays.asList(arrayPath));
            pathList.addAll(Arrays.asList(basePackages));
            // 扫描    类加载器
            // Bootstrap --> jre/lib
            // Ext --> /jre/ext/lib
            // App --> classpath
            ClassLoader classLoader = MonkeyApplicationContext.class.getClassLoader();
            for (String packagePathStr : pathList) {
                URL resource = classLoader.getResource(packagePathStr.replace(".", "/"));
                if (Objects.isNull(resource)) {
                    continue;
                }
                File file = new File(resource.getFile());
                if (file.isDirectory()) {
                    // 获取所有class文件
                    List<File> fileList = Arrays.stream(file.listFiles()).filter(f -> f.isFile())
                            .filter(f -> f.getName().endsWith("class")).collect(Collectors.toList());
                    for (File f : fileList) {
                        Class<?> clazz = loadFileInitBeanSet(f, classLoader, packagePathStr);
                        classList.add(clazz);
                    }
                    List<File> dirList = Arrays.stream(file.listFiles()).filter(f -> f.isDirectory()).collect(Collectors.toList());
                    List<File> files = scanClassFile(dirList);
                    for (File f : files) {
                        String[] filePathArray = f.getPath().split("classes");
                        int lastIndexOf = filePathArray[1].lastIndexOf("\\");
                        String path = filePathArray[1].substring(1, lastIndexOf).replace("\\", ".");
                        Class<?> clazz = loadFileInitBeanSet(f, classLoader, path);
                        classList.add(clazz);
                    }
                }
            }
            return classList;
        }
        return null;
    }

    /**
     * 递归扫描配置目录下的子目录
     *
     * @param files
     * @return
     */
    List<File> scanClassFile(List<File> files) {
        List<File> result = new ArrayList<>();
        int size = files.size();
        int i = 0;
        while (true) {
            if (i >= size) {
                break;
            }
            File file = files.get(i);
            i = i + 1;
            if (file.isDirectory()) {
                for (File listFile : file.listFiles()) {
                    if (listFile.isFile() && listFile.getName().endsWith("class")) {
                        result.add(listFile);
                    } else {
                        List<File> files1 = scanClassFile(Arrays.asList(listFile));
                        result.addAll(files1);
                    }
                }
            }

        }
        return result;
    }

    /**
     * 加载扫描bean
     *
     * @param file           file
     * @param classLoader    classLoader
     * @param packagePathStr packagePathStr
     */
    private Class<?> loadFileInitBeanSet(File file, ClassLoader classLoader, String packagePathStr) {
        String className = packagePathStr + "." + file.getName().split("\\.")[0];
        try {
            Class<?> clazz = classLoader.loadClass(className);
            return clazz;
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 处理Component注解 设置bean名称
     *
     * @param classList
     */
    private void handleComponentAnnotation(List<Class<?>> classList) {
        for (Class<?> clazz : classList) {
            if (clazz.isAnnotationPresent(Component.class)) {
                // 解析类， 判断当前bean是单例bean还是prototype bean
                Component componentAnnotation = clazz.getDeclaredAnnotation(Component.class);
                String beanName = componentAnnotation.value();
                if (clazz.isInterface() || Modifier.isAbstract(clazz.getModifiers())) {
                    log.warn("{} is interface or abstract class", beanName);
                    continue;
                }
                if (Objects.isNull(beanName) || Objects.equals("", beanName)) {
                    beanName = Introspector.decapitalize(clazz.getSimpleName());
                }
                this.checkAndHandleBeanDefinitionMapping(beanName, clazz);
            }
        }
    }

    /**
     * 检查beanDefinitionMap 并创建beanDefinition,检查bean对象是否实现了BeanPostProcessor接口
     *
     * @param beanName beanName
     * @param clazz    clazz
     */
    private void checkAndHandleBeanDefinitionMapping(String beanName, Class<?> clazz) {
        if (!beanDefinitionMap.containsKey(beanName) && !beanDefinitionNames.contains(beanName)) {
            BeanDefinition beanDefinition = new BeanDefinition();
            beanDefinitionMap.put(beanName, createBeanDefinition(beanDefinition, clazz, beanName));
            beanDefinitionNames.add(beanName);

            if (BeanPostProcessor.class.isAssignableFrom(clazz)) {
                BeanPostProcessor instance = ((BeanPostProcessor) doGetBean(beanName));
                beanPostProcessorList.add(instance);
            }
            // todo  bean对象还不存在  调用专门的处理方法
            if (!beanDefinitionByClass.containsKey(clazz)) {
                ArrayList<BeanDefinition> list = new ArrayList<>();
                list.add(beanDefinition);
                beanDefinitionByClass.put(clazz, list);
            }
        }

    }

    /**
     * 处理Importe注解
     *
     * @param configClass
     */
    private void handleImportedAnnotation(Class configClass) {
        if (configClass.isAnnotationPresent(Import.class)) {
            Import importAnn = (Import) configClass.getDeclaredAnnotation(Import.class);
            Class<?>[] classes = importAnn.value();
            try {
                for (Class<?> aClass : classes) {
                    BeanDefinition beanDefinition = new BeanDefinition();
                    String beanName = aClass.getName();
                    beanDefinition.setBeanName(beanName);
                    if (!singletonObjects.containsKey(beanName)) {
                        Object object = aClass.newInstance();
                        singletonObjects.put(beanName, object);
                        beanDefinitionNames.add(beanName);
                        ArrayList<BeanDefinition> list;
                        if (beanDefinitionByClass.containsKey(object.getClass())) {
                            list = beanDefinitionByClass.get(object.getClass());
                        } else {
                            list = new ArrayList<>();
                        }
                        list.add(beanDefinition);
                        beanDefinitionByClass.put(object.getClass(), list);
                    } else {
                        continue;
                    }
                }
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 处理Bean注解  todo 是否需要创建bean definition时创建bean
     *
     * @param configClass
     */
    private void handleBeanAnnotation(Class configClass) {

        Method[] methods = configClass.getDeclaredMethods();
        log.info("{}", Arrays.asList(methods));

        for (Method method : methods) {
            if (method.isAnnotationPresent(Bean.class)) {
                String[] values = method.getAnnotation(Bean.class).value();
                String[] names = method.getAnnotation(Bean.class).name();

                String methodName = method.getName();
                BeanDefinition beanDefinition = new BeanDefinition();
                if (method.isAnnotationPresent(Primary.class)) {
                    beanDefinition.setPrimary(true);
                }

                createBeanDefinition(beanDefinition, method.getReturnType(), method.getName());
                try {
                    if (values.length > 0 || names.length > 0) {
                        List<String> beanNameList = new ArrayList<>();
                        beanNameList.addAll(Arrays.asList(values));
                        beanNameList.addAll(Arrays.asList(names));

                        for (String beanName : beanNameList) {
                            Object target = configClass.newInstance();
                            Object object = method.invoke(target, null);
                            beanDefinitionNames.add(beanName);
                            beanDefinition.setBeanName(beanName);
                        }
                    }

                    // 获取方法中的参数类型及名称
                    Parameter[] parameters = method.getParameters();
                    if (parameters.length > 0) {
                        for (Parameter parameter : parameters) {
                            //todo
                            log.info("parameter: {}", parameter);
                        }
                    } else {
                        Object target = configClass.newInstance();
                        Object object = method.invoke(target);
                        beanDefinitionNames.add(methodName);
                        beanDefinitionMap.put(methodName, beanDefinition);
                        doBeanDefinitionTypeClass(object, beanDefinition);
                    }
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InstantiationException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
            }

        }
    }

    /**
     * 根据beanDefinition 创建Singleton Bean
     */
    private void preInstantiateSingletons() {
        try {
            for (Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) {
                String beanName = entry.getKey();
                BeanDefinition beanDefinition = entry.getValue();
                if (beanDefinition.getScope().equalsIgnoreCase(SINGLETON)) {
                    // 单例bean
                    Object bean = createBean(beanName, beanDefinition);
                    singletonObjects.put(beanName, bean);
                    beanDefinition.setBeanName(beanName);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    /**
     * 根据bean的定义创建bean对象
     *
     * @param beanDefinition beanDefinition
     * @return
     */
    private Object createBean(String beanName, BeanDefinition beanDefinition) throws Exception {
        Class clazz = beanDefinition.getType();
        Object instance = null;
        synchronized (singletonObjects) {
            try {

                // 第二次检查
                if (singletonObjects.containsKey(beanName)) {
                    return singletonObjects.get(beanName);
                }

                // 通过构造方法实例化创建对象  byType byName
                Map<Constructor, Integer> map = new HashMap<>();
                // 获取所有的构造方法
                log.warn(clazz.toString());
                Constructor[] constructors = clazz.getConstructors();
                // 构造方法和对应参数的关系
                for (Constructor constructor : constructors) {
                    int parameterCounts = constructor.getParameterCount();
                    map.put(constructor, parameterCounts);
                }
                if (map.containsValue(Integer.valueOf(0))) {
                    instance = clazz.getDeclaredConstructor().newInstance();
                } else {
                    for (Constructor constructor : constructors) {
                        boolean annotationPresent = constructor.isAnnotationPresent(Autowired.class);
                        if (annotationPresent) {
                            Parameter[] parameters = constructor.getParameters();
                            List<Object> list = new ArrayList<>();
                            Arrays.stream(parameters).forEach(p -> {
                                list.add(doGetBean(p.getName()));
                            });
                            instance = constructor.newInstance(list.toArray());
                            break;
                        }
                    }
                }
                Object singBean = instance;
                // aop 动态代理   判断只有循环依赖才创建aop     不是循环依赖不在此次创建动态代理
                singletonFactories.put(beanName, () -> new JdkProxyBeanPostProcessor().getEarlyBeanReference(singBean, beanName));
//                singletonFactories.put(beanName, () -> new JdkProxyBeanPostProcessor().getEarlyBeanReference(earlySingletonObjects.get(beanName), beanName));

                // 依赖注入 属性填充 byType byName
                for (Field declaredField : clazz.getDeclaredFields()) {
                    if (declaredField.isAnnotationPresent(Autowired.class)) {
                        Autowired annotation = declaredField.getAnnotation(Autowired.class);
                        boolean required = annotation.required();
                        Object bean = null;
                        String propertiesBeanName = "";
                        Class<?> type = declaredField.getType();
                        if (beanDefinitionByClass.containsKey(type)) {
                            ArrayList<BeanDefinition> beanDefinitions = beanDefinitionByClass.get(type);
                            if (beanDefinitions.size() > 0 && beanDefinitions.size() == 1) {
                                BeanDefinition beanDefinition1 = beanDefinitions.get(0);
                                bean = getBean(beanDefinition1.getBeanName());
                            } else {
                                propertiesBeanName = declaredField.getName();
                                bean = getBean(propertiesBeanName);
                            }
                        } else {
                            propertiesBeanName = declaredField.getName();
                            bean = getBean(propertiesBeanName);
                        }
                        if (Objects.isNull(bean) || required == false) {
                            throw new Exception(propertiesBeanName + " does not exist");
                        }
                        declaredField.setAccessible(true);
                        declaredField.set(instance, bean);
                    }
                }

                // 处理带有bean注解的方法
                handleBeanAnnotation(clazz);

                // 由于递归完后A还是愿实例，所以需要从二级缓存中获取proxy
                if (earlySingletonObjects.containsKey(beanName)) {
                    instance = earlySingletonObjects.get(beanName);
                }

                // aware 回调
                if (instance instanceof BeanNameAware) {
                    ((BeanNameAware) instance).setBeanName(beanName);
                }

                // 初始化前
                for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
                    instance = beanPostProcessor.postProcessBeforeInitialization(instance, beanName);
                }

                //初始化  InitializingBean
                if (instance instanceof InitializingBean) {
                    ((InitializingBean) instance).afterPropertiesSet();
                }

                // 初始化后
                for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
                    instance = beanPostProcessor.postProcessAfterInitialization(instance, beanName);
                }

                // 是否aop
                if (instance instanceof MonkeyAop) {
                    MonkeyDynamicProxyAopHandler jdkDynamicProxy = new MonkeyDynamicProxyAopHandler(instance);
                    instance =  jdkDynamicProxy.getProxy();
                }

                Object singleton = getSingleton(beanName);
                if (Objects.nonNull(singleton)) {
                    instance = singleton;
                }

                singletonObjects.put(beanName, instance);
                earlySingletonObjects.remove(beanName);
                singletonFactories.remove(beanName);


                return instance;
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * 创建BeanDefinition
     *
     * @param beanDefinition
     * @param clazz
     * @return
     */
    private BeanDefinition createBeanDefinition(BeanDefinition beanDefinition, Class<?> clazz, String beanName) {
        beanDefinition.setBeanName(beanName);
        beanDefinition.setType(clazz);
        if (clazz.isAnnotationPresent(Scope.class)) {
            Scope scopeAnnotation = clazz.getDeclaredAnnotation(Scope.class);
            beanDefinition.setScope(scopeAnnotation.value());
        } else {
            beanDefinition.setScope(SINGLETON);
        }

        if (clazz.isAnnotationPresent(Primary.class)) {
            beanDefinition.setPrimary(true);
        }
        return beanDefinition;
    }

    private Object getSingleton(String beanName) {
            //先从一级缓存中获取
            if (singletonObjects.containsKey(beanName)) {
                return singletonObjects.get(beanName);
            }

            if (singletonFactories.containsKey(beanName)) {
                ObjectFactory objectFactory = singletonFactories.get(beanName);
                Object object = objectFactory.getObject();
                earlySingletonObjects.put(beanName, object);
                return object;
            }



            // 一级缓存中不存在，且beanName在二级缓存中，说明是循环依赖
            /*if (Objects.isNull(bean) && singletonsCurrennlyInCreation.contains(beanName)) {
                bean = earlySingletonObjects.get(beanName);
                // 从三级缓存中获取
                ObjectFactory factory = singletonFactories.get(beanName);
                if (Objects.nonNull(factory)) {
                    // 从动态代理中获取
                    bean = factory.getObject();
                    earlySingletonObjects.put(beanName, bean);
                }
            }*/
            return null;
    }

    /**
     * 根据名称返回对象类型
     *
     * @param beanName
     * @return
     */
    private Object doGetBean(String beanName) {

        if (beanDefinitionMap.containsKey(beanName)) {
            BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
            Object bean = null;
            if (beanDefinition.getScope().equalsIgnoreCase(SINGLETON)) {
                bean = singletonObjects.get(beanName);
                if (Objects.isNull(bean)) {
                    try {
                        bean = createBean(beanName, beanDefinition);
                        beanNames.add(beanName);
                        beanDefinition.setBeanName(beanName);
                        doBeanDefinitionTypeClass(bean, beanDefinition);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            } else {
                bean = null;
                try {
                    bean = createBean(beanName, beanDefinition);
                    beanNames.add(beanName);
                    beanDefinition.setBeanName(beanName);
                    doBeanDefinitionTypeClass(bean, beanDefinition);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            return bean;
        } else {
            throw new NoClassDefFoundError();
        }
    }

    private void doBeanDefinitionTypeClass(Object bean, BeanDefinition beanDefinition) {
        ArrayList<BeanDefinition> list;
        if (beanDefinitionByClass.containsKey(bean.getClass())) {
            list = beanDefinitionByClass.get(bean.getClass());
        } else {
            list = new ArrayList();
        }
        list.add(beanDefinition);
        beanDefinitionByClass.put(bean.getClass(), list);
    }

    public Object getBean(String beanName) {
        if (beanDefinitionMap.containsKey(beanName)) {
            BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
            // 单例bean
            if (SINGLETON.equalsIgnoreCase(beanDefinition.getScope())) {
                // 先从一级缓存中获取
                Object singletonBean = getSingleton(beanName);
                if (Objects.nonNull(singletonBean)) {
                    return singletonBean;
                }

                // 正在创建
                if (!singletonsCurrennlyInCreation.contains(beanName)) {
                    singletonsCurrennlyInCreation.add(beanName);
                }
                try {
                    return createBean(beanName, beanDefinition);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            } else {
                // 原型bean
                try {
                    Object prototypeBean = createBean(beanName, beanDefinition);
                    return prototypeBean;
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        } else {
            throw new RuntimeException("Not found Bean");
        }
        return null;
    }

    /**
     * @param requriedType requriedType
     * @return
     */
    public Object getBean(Class<?> requriedType) {
        if (beanDefinitionByClass.containsKey(requriedType)) {
            AtomicReference<Object> obj = null;
            List<BeanDefinition> beanDefinitions = beanDefinitionByClass.get(requriedType);
            if (beanDefinitions.size() == 1) {
                return singletonObjects.get(beanDefinitions.get(0).getBeanName());
            } else {
                List<BeanDefinition> beanDefinitionCollect = beanDefinitions.stream()
                        .filter(beanDefinition -> beanDefinition.isPrimary()).collect(Collectors.toList());
                long count = beanDefinitionCollect.size();
                if (count > 1 || count == 0) {
                    throw new RuntimeException("Multiple Primary configuration or exist multiple bean, please check");
                } else {
                    beanDefinitions.stream().forEach(beanDefinition -> {
                        if (beanDefinition.isPrimary()) {
                            obj.set(singletonObjects.get(beanDefinition.getBeanName()));
                        }
                    });
                }
            }
            return obj;
        } else {
            throw new RuntimeException("Not found bean");
        }
    }

    public String[] getBeanDefinitionNames() {
        return beanDefinitionNames.toArray(new String[]{});
    }
}
